use sqlx::postgres::PgDatabaseError;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
    #[error("invalid start or end time for the reservation")]
    InvalidTime,

    #[error("invalid {0}: {1}")]
    InvalidId(&'static str, String),

    #[error("Database error: {0}")]
    DbError(sqlx::Error),
    // TODO:
    //DbError(#[from] sqlx::Error),
    #[error("conflict with existing reservation, {0}")]
    DbConflictReservation(String),

    #[error("not found")]
    DbNotFound,

    #[error("config error")]
    ConfigError,

    #[error("unknown error")]
    Unknown,
}

impl PartialEq for Error {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::InvalidTime, Self::InvalidTime) => true,
            (Self::InvalidId(l0, l1), Self::InvalidId(r0, r1)) => l0 == r0 && l1 == r1,
            (Self::DbError(_), Self::DbError(_)) => true,
            (Self::DbConflictReservation(l0), Self::DbConflictReservation(r0)) => l0 == r0,
            (Self::DbNotFound, Self::DbNotFound) => true,
            (Self::Unknown, Self::Unknown) => true,
            _ => false,
        }
    }
}

// 处理每种sqlx::Error的具体情况
impl From<sqlx::Error> for Error {
    fn from(error: sqlx::Error) -> Self {
        match error {
            sqlx::Error::Database(err) => {
                let e: &PgDatabaseError = err.downcast_ref();
                match (e.code(), e.schema(), e.table()) {
                    ("23P01", Some("rsvp"), Some("reservations")) => {
                        Error::DbConflictReservation(e.detail().unwrap().to_string())
                    }
                    _ => Error::DbError(sqlx::Error::Database(err)),
                }
            }
            sqlx::Error::RowNotFound => Error::DbNotFound,
            _ => Error::DbError(error),
        }
    }
}

impl From<Error> for tonic::Status {
    fn from(error: Error) -> Self {
        match error {
            Error::InvalidTime => tonic::Status::invalid_argument(error.to_string()),
            Error::InvalidId(_, _) => tonic::Status::invalid_argument(error.to_string()),
            Error::DbError(_) => tonic::Status::internal(error.to_string()),
            Error::DbConflictReservation(_) => tonic::Status::already_exists(error.to_string()),
            Error::DbNotFound => tonic::Status::not_found(error.to_string()),
            Error::ConfigError => tonic::Status::internal(error.to_string()),
            Error::Unknown => tonic::Status::unknown(error.to_string()),
        }
    }
}
